;; The first four lines of this file were added by Dracula.
;; They tell DrScheme that this is a Dracula Modular ACL2 program.
;; Leave these lines unchanged so that DrScheme can properly load this file.
#reader(planet "reader.rkt" ("cce" "dracula.plt") "modular" "lang")
(require "IcommonTimes.lisp")
(require "IAVLtree.lisp")

(module McommonTimes-private
  (import IAVLtree)
  
  (include-book "io-utilities" :dir :teachpacks)
  (include-book "list-utilities" :dir :teachpacks)
  
  
  ;STRING -> Bit Array
  
   ; Function to map a letter representing a day of the week
  ; to a number 
  ; U - Sunday    M - Monday    T - Tuesday
  ; W - Wednesday R - Thursday  F - Friday
  ; S - Saturday
  (defun map-day-of-week-to-number (day-of-week)
    (case day-of-week
      (#\U 0)
      (#\M 1)
      (#\T 2)
      (#\W 3)
      (#\R 4)
      (#\F 5)
      (#\S 6)
      (otherwise day-of-week)
      )
    )
  
    
  ; Input is a character list representing a time
  ; (ie, (#\7 #\: #\3 #\0) is 7:30) returns a 
  ; number representation, (ie, 7:30 is 7.5, 8:00 is 8)
  (defun get-time-as-float-helper(time)
    (let ((hour (chrs->str (car (break-at #\: time))))
          (minutes (chrs->str (cadr (break-at #\: time)))))
      (if (equal hour "12")
          (if (equal minutes ":30")
              (+ 0 .5)
              0
              )
          (if (equal minutes ":30")
              (+ (str->rat hour) .5)
              (str->rat hour)
              )
          )
      )
  )

  ; Input is a character list representing a time (with AM/PM)
  ; (ie, (#\7 #\: #\3 #\0 #\A #\M) is 7:30AM) returns a 
  ; number representation, (ie, 7:30AM is 7.5, 8:00AM is 8 1:00PM is 13)
  (defun get-time-as-float (time)
    (let* ((result (get-time-as-float-helper (remove #\a (remove #\p (remove #\m time))))))
      (if (member #\a time)
          result
          (+ 12 result)
      )
    )
  )
  
  
  ; Helper function for below function
  (defun get-length-and-start-of-range-helper (range)
    (let* ((start (remove #\space (car (packets #\~ range))))
           (end   (remove #\space (cadr (packets #\~ range))))
           (startDay (map-day-of-week-to-number (car start)))
           (endDay (map-day-of-week-to-number (car end)))
           (startFloat (get-time-as-float (remove #\space (cdr start))))
           (endFloat (get-time-as-float (remove #\space (cdr end))))
           (range  (* (- (+ (* endDay 24) endFloat) (+ (* startDay 24) startFloat)) 2)))
      (cons startFloat range)
    )
  )

  
  ; Returns the length of a range along with the start point
  ; (ie, (7:30am~8:30am) returns (7.5 . 2)
  (defun get-length-and-start-of-range (ranges)
    (if (consp ranges)
        (cons (get-length-and-start-of-range-helper (car ranges))
              (get-length-and-start-of-range (cdr ranges)))
        nil
        )                                        
    )
  
  ; Returns a list consisting of the number representation of a
  ; day of the week and the start/length pair (ie,
  ; "M 7:30am~8:30am" returns (1 (7.5 . 2))
  (defun get-day-and-times-pair (student-time-as-chrs)
    (list (map-day-of-week-to-number (car student-time-as-chrs))
          (get-length-and-start-of-range (list student-time-as-chrs)))
          ;(get-length-and-start-of-range (packets #\, (cdr student-time-as-chrs))))
    )
  
  ; Helper function that cycles through the list of student schedules
  ; and calls get-day-and-times-pair on them
  (defun times-from-student-as-str (student-times-as-chrs)
    (if (consp student-times-as-chrs)
        (if (or (null (car student-times-as-chrs)) (equal (car student-times-as-chrs) ""))
            (cons "" (times-from-student-as-str (cdr student-times-as-chrs)))
            (cons (get-day-and-times-pair (car student-times-as-chrs))
                  (times-from-student-as-str (cdr student-times-as-chrs)))
            )
        nil
        )
    )
  
  ; Takes in a list, a start index, and an end index and replaces
  ; all objects in those indices and the ones inbetween with x
  (defun replace (start end x xs)
    (let* ((first (car (break-at-nth start xs)))
           (second (cadr (break-at-nth (+ end 1) xs))))
      (append first (replicate (+ (- end start) 1) x) second)
      )
    )
  
  ; Takes in a day-range pair and replaces that part of the schedule
  ; with 0's
  (defun get-part-of-day (day range array)
    (replace (+ (* day 48) (* 2 (car range))) 
             (- (+ (cdr range) (+ (* day 48) (* 2 (car range)))) 1) 0 array)
    )
  
  ; Helper function for get-schedule-as-bit-array
  (defun get-schedule-as-bit-array-helper (day ranges array)
    (if (consp ranges)
        (get-schedule-as-bit-array-helper day (cdr ranges) (get-part-of-day day (car ranges) array))
        array
        )
    )
  
  ; Cycles through each schedule and creates each students
  ; schedule bit array
  (defun get-schedule-as-bit-array (schedule array)
    (if (consp schedule)
        (get-schedule-as-bit-array (cdr schedule) 
                                   (get-schedule-as-bit-array-helper 
                                    (caar schedule) (cadar schedule) array))
        array
        )
    )
  

  
  ;Given a timestirng returns a 336 size bitList
  (defun timeString->bitList(scheduleString)
    (get-schedule-as-bit-array (times-from-student-as-str (packets #\; (remove #\space (str->chrs scheduleString)))) (replicate (* 48 7) 1))
  )
  

  
  
  
  ;Bitarray -> String
  
  
  
  ;Given the daytime (0-47 = 30 min increments through day), returns the appropriate hour.
  ;Handles special logic for 12AM/PM
  (defun getHour (dayTime)
    (let* ((zeroBasedHour (mod (floor dayTime 2) 12)))
      (if (equal zeroBasedHour 0)
          12
          zeroBasedHour
      )
    )
  )
  
  
  ;Given the current counter value, returns a single character string representing the day of the week.
  (defun countertoday (counter)
    (let* ((flooor (floor counter 48)))
      (cond ((equal flooor 0) "U")
            ((equal flooor 1) "M")
            ((equal flooor 2) "T")
            ((equal flooor 3) "W")
            ((equal flooor 4) "R")
            ((equal flooor 5) "F")
            ((equal flooor 6) "S")
            (t "Error")
      )
    )
  )
  
  ;Given the current counter value, returns the corresponding time as a string.
  (defun countertotime (counter)
    (let* ((day (countertoday counter))
           (dayTime (mod counter 48))
           (half (mod dayTime 2))
           (isAm (< dayTime 24)))
      (concatenate 'string
                   day
                   " "
                   (rat->str (getHour dayTime) 0)
                   ":"
                   (if (equal half 0)
                       "00"
                       "30"
                   )
                   (if isAm
                       "am"
                       "pm"
                   )
      )
    )
  )
  
  ;Uses starting / ending counter indecies to determine the appropriate time/date range and returns it as a string.
  (defun countertotimeslot (start end)
    (concatenate 'string (countertotime start) " ~ " (countertotime end))
  )
  
  ;Given a list of bits and the starting/ending indecies from the entire bit list,
  ;Outputs the appropiate time string.
  (defun getonetime (bits start end)
    (if (or (equal (car bits) 1) (null bits))
        (list (countertotimeslot start end) bits end)
        (getonetime (cdr bits) start (+ end 1))
    )
  )
  
  ;Helper method for determinging the appropriate string output for a bitlist.
  ;Uses a counter to keep track of the current bit
  (defun bitlist->timesstringHelper (bits counter)
    (if (null bits)
        nil
        (if (equal (car bits) 0)
            ; found a new set of times
            (let* ((ret (getonetime bits counter counter))
                   (timestr (car ret))
                   (rembits (cadr ret))
                   (counter (caddr ret)))
              (cons timestr (bitlist->timesstringHelper rembits counter))
            )
            (bitlist->timesstringHelper (cdr bits) (+ counter 1))
        )
     )
  )
  

  ;Given a list of times, concatenates them into a single long string (seperated by semicolons).
  ;Used for output purposes. Takes in a first paramter indicating whether or not semicolons should be added
  (defun concatenateListOfStringsHelper(times first)
    (if (consp times)
        (if (equal first 1)
            (concatenate 'string (car times) (concatenateListOfStringsHelper (cdr times) nil))
            (concatenate 'string "; " (car times) (concatenateListOfStringsHelper (cdr times) nil))
        )
        ""
    )
  )
  
  ;Given a list of times, concatenates them into a single long string (seperated by semicolons).
  ;Used for output purposes.
  (defun concatenateListOfStrings(times)
    (concatenateListOfStringsHelper times 1)
  )
  
  
  ;Takes in a list of bits and outputs the corresponding time(s) as a string.
  (defun bitlist->timesstring (bits)
    (let* ((times (bitlist->timesstringHelper bits 0)))
      (concatenateListOfStrings times)
    )
  )
  
  
  
  
  ;Performs the AND operation on a single bit.
  ;Bits are reversed from standard convention in that 0 is true and 1 is false.
  ;We only return 0 (true) if timeBit1 and timeBit2 are both 0 (true).
  (defun time-bit-and(timeBit1 timeBit2)
    (if (and (equal timeBit1 0) (equal timeBit2 0))
        0
        1
    )
  )
    
  
  ;Performs the bitwise AND operation between two time bit arrays.
  (defun time-bitwise-and(time1 time2)
    (if (and (consp time1) (consp time2))
        (cons (time-bit-and (car time1) (car time2)) (time-bitwise-and (cdr time1) (cdr time2)))
        (list nil)
    )
  )
        
  
  ;Takes in a list of group members from the avl tree and returns a string output listing all common times between group members.
  (defun get-common-times-output (members)
    (if (consp members)
        (time-bitwise-and (nth 5 (car members)) (get-common-times-output (cdr members)))
        (replicate (* 48 7) 0)
    )
  )
  
  (export IcommonTimes)
)

(link McommonTimes
  (import IAVLtree)
  (export IcommonTimes)
  (McommonTimes-private))